package io.questdb.std;

import io.questdb.std.str.CharSink;
import io.questdb.std.str.Sinkable;
import io.questdb.std.str.StringSink;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;

/**
 * Decimal128 - a mutable decimal number implementation. The value is a signed number with
 * two's complement representation.
 * <p>
 * This class represents decimal numbers with a fixed scale (number of decimal places)
 * using 128-bit integer arithmetic for precise calculations. All operations are
 * performed in-place to eliminate object allocation and improve performance.
 * </p>
 * <p>
 * This type tries to but doesn't follow IEEE 754; one of the main goals is using 128 bits to store
 * the sign and trailing significant field (T).
 * Using 1 bit for the sign, we have 127 bits for T, which gives us 38 digits of precision.
 * </p>
 */
public class Decimal128 implements Sinkable, Decimal {
    /**
     * Maximum allowed precision (number of digits)
     */
    public static final int MAX_PRECISION = 38;
    /**
     * Maximum allowed scale (number of decimal places)
     */
    public static final int MAX_SCALE = 38;
    public static final Decimal128 MAX_VALUE = new Decimal128(5421010862427522170L, 687399551400673279L);
    public static final Decimal128 MIN_VALUE = new Decimal128(-5421010862427522171L, -687399551400673279L);
    public static final Decimal128 NULL_VALUE = new Decimal128(Decimals.DECIMAL128_HI_NULL, Decimals.DECIMAL128_LO_NULL);
    public static final Decimal128 ZERO = new Decimal128(0, 0, 0);
    static final long LONG_MASK = 0xffffffffL;
    /**
     * Pre-computed powers of 10 table for decimal arithmetic.
     * Autogenerated. See `Decimal128Test.testPowersTenTable`.
     *
     * <p>
     * This table stores the 128-bit representation of powers of 10 (10^0 to 10^38)
     * multiplied by digits 1-9. Each row contains 9 complete 128-bit values, where
     * each 128-bit value is represented as 2 consecutive 64-bit longs:
     * - High (bits 127-64)
     * - Low (bits 63-0, least significant 64 bits)
     *
     * <p>
     * Structure: table[power][multiplier_offset + component]
     * - power: power of 10 (0 = 10^0, 1 = 10^1, ..., 38 = 10^38)
     * - multiplier_offset: (multiplier-1) * 2, where multiplier ∈ [1,9]
     * - component: 0=High, 1=Low
     *
     * <p>
     * Example: To get 7 × 10^25:
     * - Row: table[25]
     * - Offset: (7-1) * 2 = 12
     * - Access: High=table[25][12], Low=table[25][13]
     * - Value: (High << 64) | Low
     */
    private static final long[][] POWERS_TEN_TABLE = new long[][]{
            {0L, 1L, 0L, 2L, 0L, 3L, 0L, 4L, 0L, 5L, 0L, 6L, 0L, 7L, 0L, 8L, 0L, 9L},
            {0L, 10L, 0L, 20L, 0L, 30L, 0L, 40L, 0L, 50L, 0L, 60L, 0L, 70L, 0L, 80L, 0L, 90L},
            {0L, 100L, 0L, 200L, 0L, 300L, 0L, 400L, 0L, 500L, 0L, 600L, 0L, 700L, 0L, 800L, 0L, 900L},
            {0L, 1000L, 0L, 2000L, 0L, 3000L, 0L, 4000L, 0L, 5000L, 0L, 6000L, 0L, 7000L, 0L, 8000L, 0L, 9000L},
            {0L, 10000L, 0L, 20000L, 0L, 30000L, 0L, 40000L, 0L, 50000L, 0L, 60000L, 0L, 70000L, 0L, 80000L, 0L, 90000L},
            {0L, 100000L, 0L, 200000L, 0L, 300000L, 0L, 400000L, 0L, 500000L, 0L, 600000L, 0L, 700000L, 0L, 800000L, 0L, 900000L},
            {0L, 1000000L, 0L, 2000000L, 0L, 3000000L, 0L, 4000000L, 0L, 5000000L, 0L, 6000000L, 0L, 7000000L, 0L, 8000000L, 0L, 9000000L},
            {0L, 10000000L, 0L, 20000000L, 0L, 30000000L, 0L, 40000000L, 0L, 50000000L, 0L, 60000000L, 0L, 70000000L, 0L, 80000000L, 0L, 90000000L},
            {0L, 100000000L, 0L, 200000000L, 0L, 300000000L, 0L, 400000000L, 0L, 500000000L, 0L, 600000000L, 0L, 700000000L, 0L, 800000000L, 0L, 900000000L},
            {0L, 1000000000L, 0L, 2000000000L, 0L, 3000000000L, 0L, 4000000000L, 0L, 5000000000L, 0L, 6000000000L, 0L, 7000000000L, 0L, 8000000000L, 0L, 9000000000L},
            {0L, 10000000000L, 0L, 20000000000L, 0L, 30000000000L, 0L, 40000000000L, 0L, 50000000000L, 0L, 60000000000L, 0L, 70000000000L, 0L, 80000000000L, 0L, 90000000000L},
            {0L, 100000000000L, 0L, 200000000000L, 0L, 300000000000L, 0L, 400000000000L, 0L, 500000000000L, 0L, 600000000000L, 0L, 700000000000L, 0L, 800000000000L, 0L, 900000000000L},
            {0L, 1000000000000L, 0L, 2000000000000L, 0L, 3000000000000L, 0L, 4000000000000L, 0L, 5000000000000L, 0L, 6000000000000L, 0L, 7000000000000L, 0L, 8000000000000L, 0L, 9000000000000L},
            {0L, 10000000000000L, 0L, 20000000000000L, 0L, 30000000000000L, 0L, 40000000000000L, 0L, 50000000000000L, 0L, 60000000000000L, 0L, 70000000000000L, 0L, 80000000000000L, 0L, 90000000000000L},
            {0L, 100000000000000L, 0L, 200000000000000L, 0L, 300000000000000L, 0L, 400000000000000L, 0L, 500000000000000L, 0L, 600000000000000L, 0L, 700000000000000L, 0L, 800000000000000L, 0L, 900000000000000L},
            {0L, 1000000000000000L, 0L, 2000000000000000L, 0L, 3000000000000000L, 0L, 4000000000000000L, 0L, 5000000000000000L, 0L, 6000000000000000L, 0L, 7000000000000000L, 0L, 8000000000000000L, 0L, 9000000000000000L},
            {0L, 10000000000000000L, 0L, 20000000000000000L, 0L, 30000000000000000L, 0L, 40000000000000000L, 0L, 50000000000000000L, 0L, 60000000000000000L, 0L, 70000000000000000L, 0L, 80000000000000000L, 0L, 90000000000000000L},
            {0L, 100000000000000000L, 0L, 200000000000000000L, 0L, 300000000000000000L, 0L, 400000000000000000L, 0L, 500000000000000000L, 0L, 600000000000000000L, 0L, 700000000000000000L, 0L, 800000000000000000L, 0L, 900000000000000000L},
            {0L, 1000000000000000000L, 0L, 2000000000000000000L, 0L, 3000000000000000000L, 0L, 4000000000000000000L, 0L, 5000000000000000000L, 0L, 6000000000000000000L, 0L, 7000000000000000000L, 0L, 8000000000000000000L, 0L, 9000000000000000000L},
            {0L, -8446744073709551616L, 1L, 1553255926290448384L, 1L, -6893488147419103232L, 2L, 3106511852580896768L, 2L, -5340232221128654848L, 3L, 4659767778871345152L, 3L, -3786976294838206464L, 4L, 6213023705161793536L, 4L, -2233720368547758080L},
            {5L, 7766279631452241920L, 10L, -2914184810805067776L, 16L, 4852094820647174144L, 21L, -5828369621610135552L, 27L, 1937910009842106368L, 32L, -8742554432415203328L, 37L, -976274800962961408L, 43L, 6790004830489280512L, 48L, -3890459611768029184L},
            {54L, 3875820019684212736L, 108L, 7751640039368425472L, 162L, -6819284014656913408L, 216L, -2943463994972700672L, 271L, 932356024711512064L, 325L, 4808176044395724800L, 379L, 8683996064079937536L, 433L, -5886927989945401344L, 487L, -2011107970261188608L},
            {542L, 1864712049423024128L, 1084L, 3729424098846048256L, 1626L, 5594136148269072384L, 2168L, 7458848197692096512L, 2710L, -9123183826594430976L, 3252L, -7258471777171406848L, 3794L, -5393759727748382720L, 4336L, -3529047678325358592L, 4878L, -1664335628902334464L},
            {5421L, 200376420520689664L, 10842L, 400752841041379328L, 16263L, 601129261562068992L, 21684L, 801505682082758656L, 27105L, 1001882102603448320L, 32526L, 1202258523124137984L, 37947L, 1402634943644827648L, 43368L, 1603011364165517312L, 48789L, 1803387784686206976L},
            {54210L, 2003764205206896640L, 108420L, 4007528410413793280L, 162630L, 6011292615620689920L, 216840L, 8015056820827586560L, 271050L, -8427923047675068416L, 325260L, -6424158842468171776L, 379470L, -4420394637261275136L, 433680L, -2416630432054378496L, 487890L, -412866226847481856L},
            {542101L, 1590897978359414784L, 1084202L, 3181795956718829568L, 1626303L, 4772693935078244352L, 2168404L, 6363591913437659136L, 2710505L, 7954489891797073920L, 3252606L, -8901356203553062912L, 3794707L, -7310458225193648128L, 4336808L, -5719560246834233344L, 4878909L, -4128662268474818560L},
            {5421010L, -2537764290115403776L, 10842021L, -5075528580230807552L, 16263032L, -7613292870346211328L, 21684043L, 8295686913247936512L, 27105054L, 5757922623132532736L, 32526065L, 3220158333017128960L, 37947076L, 682394042901725184L, 43368086L, -1855370247213678592L, 48789097L, -4393134537329082368L},
            {54210108L, -6930898827444486144L, 108420217L, 4584946418820579328L, 162630325L, -2345952408623906816L, 216840434L, 9169892837641158656L, 271050543L, 2238994010196672512L, 325260651L, -4691904817247813632L, 379470760L, 6823940429017251840L, 433680868L, -106958398427234304L, 487890977L, -7037857225871720448L},
            {542101086L, 4477988020393345024L, 1084202172L, 8955976040786690048L, 1626303258L, -5012780012529516544L, 2168404344L, -534791992136171520L, 2710505431L, 3943196028257173504L, 3252606517L, 8421184048650518528L, 3794707603L, -5547572004665688064L, 4336808689L, -1069583984272343040L, 4878909776L, 3408404036121001984L},
            {5421010862L, 7886392056514347008L, 10842021724L, -2673959960680857600L, 16263032587L, 5212432095833489408L, 21684043449L, -5347919921361715200L, 27105054312L, 2538472135152631808L, 32526065174L, -8021879882042572800L, 37947076036L, -135487825528225792L, 43368086899L, 7750904230986121216L, 48789097761L, -2809447786209083392L},
            {54210108624L, 5076944270305263616L, 108420217248L, -8292855533099024384L, 162630325872L, -3215911262793760768L, 216840434497L, 1861033007511502848L, 271050543121L, 6937977277816766464L, 325260651745L, -6431822525587521536L, 379470760369L, -1354878255282257920L, 433680868994L, 3722066015023005696L, 487890977618L, 8799010285328269312L},
            {542101086242L, -4570789518076018688L, 1084202172485L, -9141579036152037376L, 1626303258728L, 4734375519481495552L, 2168404344971L, 163586001405476864L, 2710505431213L, -4407203516670541824L, 3252606517456L, -8977993034746560512L, 3794707603699L, 4897961520886972416L, 4336808689942L, 327172002810953728L, 4878909776184L, -4243617515265064960L},
            {5421010862427L, -8814407033341083648L, 10842021724855L, 817930007027384320L, 16263032587282L, -7996477026313699328L, 21684043449710L, 1635860014054768640L, 27105054312137L, -7178547019286315008L, 32526065174565L, 2453790021082152960L, 37947076036992L, -6360617012258930688L, 43368086899420L, 3271720028109537280L, 48789097761847L, -5542687005231546368L},
            {54210108624275L, 4089650035136921600L, 108420217248550L, 8179300070273843200L, 162630325872825L, -6177793968298786816L, 216840434497100L, -2088143933161865216L, 271050543121376L, 2001506101975056384L, 325260651745651L, 6091156137111977984L, 379470760369926L, -8265937901460652032L, 433680868994201L, -4176287866323730432L, 487890977618476L, -86637831186808832L},
            {542101086242752L, 4003012203950112768L, 1084202172485504L, 8006024407900225536L, 1626303258728256L, -6437707461859213312L, 2168404344971008L, -2434695257909100544L, 2710505431213761L, 1568316946041012224L, 3252606517456513L, 5571329149991124992L, 3794707603699265L, -8872402719768313856L, 4336808689942017L, -4869390515818201088L, 4878909776184769L, -866378311868088320L},
            {5421010862427522L, 3136633892082024448L, 10842021724855044L, 6273267784164048896L, 16263032587282566L, -9036842397463478272L, 21684043449710088L, -5900208505381453824L, 27105054312137610L, -2763574613299429376L, 32526065174565133L, 373059278782595072L, 37947076036992655L, 3509693170864619520L, 43368086899420177L, 6646327062946643968L, 48789097761847699L, -8663783118680883200L},
            {54210108624275221L, -5527149226598858752L, 108420217248550443L, 7392445620511834112L, 162630325872825665L, 1865296393912975360L, 216840434497100886L, -3661852832685883392L, 271050543121376108L, -9189002059284742144L, 325260651745651330L, 3730592787825950720L, 379470760369926551L, -1796556438772908032L, 433680868994201773L, -7323705665371766784L, 487890977618476995L, 5595889181738926080L},
            {542101086242752217L, 68739955140067328L, 1084202172485504434L, 137479910280134656L, 1626303258728256651L, 206219865420201984L, 2168404344971008868L, 274959820560269312L, 2710505431213761085L, 343699775700336640L, 3252606517456513302L, 412439730840403968L, 3794707603699265519L, 481179685980471296L, 4336808689942017736L, 549919641120538624L, 4878909776184769953L, 618659596260605952L}
    };
    private static final long[][] POWERS_TEN_TABLE_THRESHOLDS = new long[][]{
            {542101086242752217L, 68739955140067327L},
            {54210108624275221L, -5527149226598858753L},
            {5421010862427522L, 3136633892082024447L},
            {542101086242752L, 4003012203950112767L},
            {54210108624275L, 4089650035136921599L},
            {5421010862427L, -8814407033341083649L},
            {542101086242L, -4570789518076018689L},
            {54210108624L, 5076944270305263615L},
            {5421010862L, 7886392056514347007L},
            {542101086L, 4477988020393345023L},
            {54210108L, -6930898827444486145L},
            {5421010L, -2537764290115403777L},
            {542101L, 1590897978359414783L},
            {54210L, 2003764205206896639L},
            {5421L, 200376420520689663L},
            {542L, 1864712049423024127L},
            {54L, 3875820019684212735L},
            {5L, 7766279631452241919L},
            {0L, -8446744073709551617L},
            {0L, 999999999999999999L},
            {0L, 99999999999999999L},
            {0L, 9999999999999999L},
            {0L, 999999999999999L},
            {0L, 99999999999999L},
            {0L, 9999999999999L},
            {0L, 999999999999L},
            {0L, 99999999999L},
            {0L, 9999999999L},
            {0L, 999999999L},
            {0L, 99999999L},
            {0L, 9999999L},
            {0L, 999999L},
            {0L, 99999L},
            {0L, 9999L},
            {0L, 999L},
            {0L, 99L},
            {0L, 9L}
    };
    private static final long[] TEN_POWERS_TABLE_HIGH = { // High 64-bit part of the ten powers table from 10^20 to 10^38
            5L, // 10^20
            54L, // 10^21
            542L, // 10^22
            5421L, // 10^23
            54210L, // 10^24
            542101L, // 10^25
            5421010L, // 10^26
            54210108L, // 10^27
            542101086L, // 10^28
            5421010862L, // 10^29
            54210108624L, // 10^30
            542101086242L, // 10^31
            5421010862427L, // 10^32
            54210108624275L, // 10^33
            542101086242752L, // 10^34
            5421010862427522L, // 10^35
            54210108624275221L, // 10^36
            542101086242752217L, // 10^37
            5421010862427522170L, // 10^38
    };
    private static final long[] TEN_POWERS_TABLE_LOW = { // Low 64-bit part of the ten powers table from 10^0 to 10^38
            1L, // 10^0
            10L, // 10^1
            100L, // 10^2
            1000L, // 10^3
            10000L, // 10^4
            100000L, // 10^5
            1000000L, // 10^6
            10000000L, // 10^7
            100000000L, // 10^8
            1000000000L, // 10^9
            10000000000L, // 10^10
            100000000000L, // 10^11
            1000000000000L, // 10^12
            10000000000000L, // 10^13
            100000000000000L, // 10^14
            1000000000000000L, // 10^15
            10000000000000000L, // 10^16
            100000000000000000L, // 10^17
            1000000000000000000L, // 10^18
            -8446744073709551616L, // 10^19
            7766279631452241920L, // 10^20
            3875820019684212736L, // 10^21
            1864712049423024128L, // 10^22
            200376420520689664L, // 10^23
            2003764205206896640L, // 10^24
            1590897978359414784L, // 10^25
            -2537764290115403776L, // 10^26
            -6930898827444486144L, // 10^27
            4477988020393345024L, // 10^28
            7886392056514347008L, // 10^29
            5076944270305263616L, // 10^30
            -4570789518076018688L, // 10^31
            -8814407033341083648L, // 10^32
            4089650035136921600L, // 10^33
            4003012203950112768L, // 10^34
            3136633892082024448L, // 10^35
            -5527149226598858752L, // 10^36
            68739955140067328L, // 10^37
            687399551400673280L, // 10^38
    };
    private final DecimalKnuthDivider divider = new DecimalKnuthDivider();
    private long high;  // High 64 bits
    private long low;   // Low 64 bits
    private int scale;  // Number of decimal places

    /**
     * Default constructor - creates zero with scale 0
     */
    public Decimal128() {
        this.high = 0;
        this.low = 0;
        this.scale = 0;
    }

    public Decimal128(long high, long low) {
        this.high = high;
        this.low = low;
        this.scale = 0;
    }

    /**
     * Constructor with initial values.
     *
     * @param high  the high 64 bits of the decimal value
     * @param low   the low 64 bits of the decimal value
     * @param scale the number of decimal places
     * @throws NumericException if scale is invalid
     */
    public Decimal128(long high, long low, int scale) {
        validateScale(scale);
        this.high = high;
        this.low = low;
        this.scale = scale;
        if (hasUnsignOverflowed()) {
            throw NumericException.instance().put("Overflow in multiplication: result exceeds maximum precision");
        }
    }

    /* ---------- helpers (use built-in power-of-ten tables) ---------- */

    /**
     * Add two Decimal128 numbers and store the result in sink
     *
     * @param a    First operand
     * @param b    Second operand
     * @param sink Destination for the result
     */
    public static void add(Decimal128 a, Decimal128 b, Decimal128 sink) {
        sink.copyFrom(a);
        sink.add(b);
    }

    /**
     * Compares 2 Decimal128, ignoring scaling.
     */
    public static int compare(long aHi, long aLo, long bHi, long bLo) {
        int s = Long.compare(aHi, bHi);
        if (s != 0) {
            return s;
        }
        return Long.compareUnsigned(aLo, bLo);
    }

    public static int compare(Decimal128 a, Decimal128 b) {
        int s = Long.compare(a.getHigh(), b.getHigh());
        if (s != 0) {
            return s;
        }
        return Long.compareUnsigned(a.getLow(), b.getLow());
    }

    public static int compare(Decimal128 a, long bHi, long bLo) {
        return compare(a.getHigh(), a.getLow(), bHi, bLo);
    }

    public static int compare(long aHi, long aLo, Decimal128 b) {
        return compare(aHi, aLo, b.getHigh(), b.getLow());
    }

    public static int compareTo(long aHigh, long aLow, int aScale, long bHigh, long bLow, int bScale) {
        if (isNull(aHigh, aLow)) {
            if (isNull(bHigh, bLow)) {
                return 0;
            }
            return -1;
        }
        if (isNull(bHigh, bLow)) {
            return 1;
        }

        boolean aNeg = aHigh < 0;
        boolean bNeg = bHigh < 0;
        if (aNeg != bNeg) {
            return aNeg ? -1 : 1;
        }

        if (aScale == bScale) {
            // Same scale - direct comparison
            return compare(aHigh, aLow, bHigh, bLow);
        }

        // We need to make both operands positive to detect overflows when scaling them
        if (aNeg) {
            aLow = ~aLow + 1;
            aHigh = ~aHigh + (aLow == 0 ? 1L : 0L);

            // Negate b
            bLow = ~bLow + 1;
            bHigh = ~bHigh + (bLow == 0 ? 1L : 0L);
        }

        // Different scales - need to align for comparison
        // We'll scale up the one with smaller scale
        Decimal128 holder = Misc.getThreadLocalDecimal128();
        if (aScale < bScale) {
            holder.of(aHigh, aLow, aScale);
            holder.multiplyByPowerOf10InPlace(bScale - aScale);
            aHigh = holder.high;
            aLow = holder.low;
        } else {
            holder.of(bHigh, bLow, bScale);
            holder.multiplyByPowerOf10InPlace(aScale - bScale);
            bHigh = holder.high;
            bLow = holder.low;
        }

        return compare(aHigh, aLow, bHigh, bLow) * (aNeg ? -1 : 1);
    }

    /**
     * Divide two Decimal128 numbers and store the result in sink (a / b -> sink)
     * Uses optimal precision calculation up to MAX_SCALE
     *
     * @param a            First operand (dividend)
     * @param b            Second operand (divisor)
     * @param sink         Destination for the result
     * @param scale        Result scale
     * @param roundingMode Rounding Mode used to round the result
     */
    public static void divide(Decimal128 a, Decimal128 b, Decimal128 sink, int scale, RoundingMode roundingMode) {
        sink.copyFrom(a);
        sink.divide(b, scale, roundingMode);
    }

    /**
     * Create a Decimal128 from a BigDecimal value.
     *
     * @param value the BigDecimal value to convert
     * @return a new Decimal128 representing the BigDecimal value
     * @throws NumericException if scale is invalid
     */
    public static Decimal128 fromBigDecimal(BigDecimal value) {
        if (value == null) {
            throw NumericException.instance().put("BigDecimal value cannot be null");
        }

        int scale = value.scale();
        long hi;
        long lo;
        BigInteger bi = value.unscaledValue();
        if (scale < 0) {
            // We don't support negative scale, we must transform the value to match
            // our format.
            bi = bi.multiply(new BigInteger("10").pow(-scale));
            scale = 0;
        }
        if (bi.bitLength() > 127) {
            throw NumericException.instance().put("Overflow: BigDecimal value exceeds 128-bit capacity during conversion");
        }
        lo = bi.longValue();
        hi = bi.shiftRight(64).longValue();
        validateScale(scale);
        return new Decimal128(hi, lo, scale);
    }

    /**
     * Create a Decimal128 from a double value, using HALF_UP when rounding is needed.
     *
     * @param value the double value to convert
     * @param scale the number of decimal places
     * @return a new Decimal128 representing the double value
     * @throws NumericException if scale is invalid
     */
    public static Decimal128 fromDouble(double value, int scale) {
        validateScale(scale);
        BigDecimal bd = new BigDecimal(value).setScale(scale, RoundingMode.HALF_UP);
        return fromBigDecimal(bd);
    }

    /**
     * Create a Decimal128 from a long value.
     *
     * @param value the long value to convert
     * @param scale the number of decimal places
     * @return a new Decimal128 representing the long value
     * @throws NumericException if scale is invalid
     */
    public static Decimal128 fromLong(long value, int scale) {
        validateScale(scale);
        long h = value < 0 ? -1L : 0L;
        return new Decimal128(h, value, scale);
    }

    /**
     * Extracts the digit at a specific power-of-ten position from a 128-bit decimal number.
     * <p>
     * Uses binary search to efficiently determine which digit (0-9) should appear at the
     * given power-of-ten position when the decimal is represented in base 10.
     * <p>
     * Prerequisites:
     * - The decimal value must be positive
     * - The decimal value must be less than 10^pow (e.g., for pow=3, decimal must be &lt; 10000)
     *
     * @param high high 64 bits of the 128-bit decimal
     * @param low  low 64 bits of the 128-bit decimal
     * @param pow  the power of ten position to extract (0 = ones place, 1 = tens place, etc.)
     * @return the digit (0-9) at the specified power-of-ten position
     */
    public static int getDigitAtPowerOfTen(long high, long low, int pow) {
        // We do a binary search to retrieve the digit we need to display at a specific power
        if (compareToPowerOfTen(high, low, pow, 5) >= 0) {
            if (compareToPowerOfTen(high, low, pow, 7) >= 0) {
                if (compareToPowerOfTen(high, low, pow, 9) >= 0) {
                    return 9;
                } else if (compareToPowerOfTen(high, low, pow, 8) >= 0) {
                    return 8;
                } else {
                    return 7;
                }
            } else {
                if (compareToPowerOfTen(high, low, pow, 6) >= 0) {
                    return 6;
                } else {
                    return 5;
                }
            }
        } else {
            if (compareToPowerOfTen(high, low, pow, 3) >= 0) {
                if (compareToPowerOfTen(high, low, pow, 4) >= 0) {
                    return 4;
                } else {
                    return 3;
                }
            } else {
                if (compareToPowerOfTen(high, low, pow, 2) >= 0) {
                    return 2;
                } else if (compareToPowerOfTen(high, low, pow, 1) >= 0) {
                    return 1;
                }
            }
        }
        return 0;
    }

    @TestOnly
    public static long[][] getPowersTenTable() {
        return POWERS_TEN_TABLE;
    }

    @TestOnly
    public static long[][] getPowersTenThresholdsTable() {
        return POWERS_TEN_TABLE_THRESHOLDS;
    }

    /**
     * Check whether the given decimal128 is null or not.
     *
     * @return true if the value is null.
     */
    public static boolean isNull(long hi, long lo) {
        return hi == Decimals.DECIMAL128_HI_NULL && lo == Decimals.DECIMAL128_LO_NULL;
    }

    /**
     * Calculate modulo of two Decimal128 numbers and store the result in sink (a % b -> sink)
     *
     * @param a    First operand (dividend)
     * @param b    Second operand (divisor)
     * @param sink Destination for the result
     */
    public static void modulo(Decimal128 a, Decimal128 b, Decimal128 sink) {
        sink.copyFrom(a);
        sink.modulo(b);
    }

    /**
     * Multiply two Decimal128 numbers and store the result in sink
     *
     * @param a    First operand
     * @param b    Second operand
     * @param sink Destination for the result
     */
    public static void multiply(Decimal128 a, Decimal128 b, Decimal128 sink) {
        sink.copyFrom(a);
        sink.multiply(b);
    }

    /**
     * Negate a Decimal128 number and store the result in sink
     *
     * @param a    Input operand to negate
     * @param sink Destination for the result
     */
    public static void negate(Decimal128 a, Decimal128 sink) {
        sink.copyFrom(a);
        sink.negate();
    }

    public static void put(Decimal128 d, long addr) {
        put(d.high, d.low, addr);
    }

    public static void put(long high, long low, long addr) {
        Unsafe.getUnsafe().putLong(addr, high);
        Unsafe.getUnsafe().putLong(addr + Long.BYTES, low);
    }

    public static void putNull(long addr) {
        Unsafe.getUnsafe().putLong(addr, Decimals.DECIMAL128_HI_NULL);
        Unsafe.getUnsafe().putLong(addr + 8L, Decimals.DECIMAL128_LO_NULL);
    }

    /**
     * Subtract two Decimal128 numbers and store the result in sink (a - b -> sink)
     *
     * @param a    First operand (minuend)
     * @param b    Second operand (subtrahend)
     * @param sink Destination for the result
     */
    public static void subtract(Decimal128 a, Decimal128 b, Decimal128 sink) {
        sink.copyFrom(a);
        sink.subtract(b);
    }

    /**
     * Writes the string representation of the given Decimal256 to the specified CharSink.
     * The output format is a plain decimal string without scientific notation.
     *
     * @param sink the CharSink to write to
     */
    public static void toSink(@NotNull CharSink<?> sink, long high, long low, int scale) {
        toSink(sink, high, low, scale, MAX_PRECISION);
    }

    /**
     * Writes the string representation of the given Decimal256 to the specified CharSink.
     * The output format is a plain decimal string without scientific notation.
     *
     * @param sink the CharSink to write to
     */
    public static void toSink(@NotNull CharSink<?> sink, long high, long low, int scale, int precision) {
        if (isNull(high, low)) {
            return;
        }

        if (high < 0) {
            low = ~low + 1;
            high = ~high + (low == 0L ? 1L : 0L);
            sink.put('-');
        }

        boolean printed = false;
        for (int i = precision - 1; i >= 0; i--) {
            if (i == scale - 1) {
                if (!printed) {
                    sink.put('0');
                }
                printed = true;
                sink.put('.');
            }

            // Fast path, we expect most digits to be 0
            if (compareToPowerOfTen(high, low, i, 1) < 0) {
                if (printed) {
                    sink.put('0');
                }
                continue;
            }

            int mul = getDigitAtPowerOfTen(high, low, i);
            sink.putAscii((char) ('0' + mul));
            printed = true;

            // Subtract the value and continue again
            int offset = (mul - 1) * 2;

            long bLow = ~POWERS_TEN_TABLE[i][offset + 1] + 1;
            long c = bLow == 0L ? 1L : 0L;
            long r = low + bLow;
            long carry = hasCarry(low, r) ? 1L : 0L;
            low = r;

            long bHigh = ~POWERS_TEN_TABLE[i][offset] + c;
            high += carry + bHigh;
        }

        if (!printed) {
            sink.put('0');
        }
    }

    /**
     * Adds a 64-bit two's-complement value {@code b} to {@code result} in place without performing overflow checks.
     * <p>
     * The {@code long} operand is treated as a Decimal64 that already shares the same scale as {@code result};
     * callers must guarantee the scale alignment and that the sum remains representable as a {@link Decimal128}.
     *
     * @param result accumulator mutated with the addition
     * @param b      Decimal64 addend stored in a signed 64-bit integer
     */
    public static void uncheckedAdd(Decimal128 result, long b) {
        long r = result.low + b;
        long carry = hasCarry(result.low, r) ? 1L : 0L;
        result.low = r;
        result.high += carry + (b < 0 ? -1L : 0L);
    }

    public static void uncheckedAdd(Decimal128 result, Decimal128 other) {
        uncheckedAdd(result, other.high, other.low);
    }

    /**
     * Add another Decimal128 to this one (in-place)
     *
     * @param other the Decimal128 to add
     */
    public void add(Decimal128 other) {
        add(other.high, other.low, other.scale);
    }

    /**
     * Add another Decimal128 to this one (in-place)
     */
    public void add(long otherHigh, long otherLow, int otherScale) {
        add(this, this.high, this.low, this.scale, otherHigh, otherLow, otherScale);
    }

    @Override
    public void addPowerOfTenMultiple(int pow, int multiplier) {
        if (multiplier == 0 || multiplier > 9) {
            return;
        }

        final int offset = (multiplier - 1) * 2;
        final long bHi = Decimal128.POWERS_TEN_TABLE[pow][offset];
        final long bLo = Decimal128.POWERS_TEN_TABLE[pow][offset + 1];
        uncheckedAdd(this, bHi, bLo);
    }

    /**
     * Compare this to another Decimal128 (handles different scales).
     *
     * @param other the Decimal128 to compare with
     * @return -1 if this decimal is less than other, 0 if equal, 1 if greater than other
     */
    public int compareTo(Decimal128 other) {
        return compareTo(other.high, other.low, other.scale);
    }

    public int compareTo(long otherHigh, long otherLow, int otherScale) {
        return compareTo(high, low, scale, otherHigh, otherLow, otherScale);
    }

    /**
     * Copy values from another Decimal128
     */
    public void copyFrom(Decimal128 source) {
        this.high = source.high;
        this.low = source.low;
        this.scale = source.scale;
    }

    /**
     * Copy values from another Decimal128 instance without the scale.
     *
     * @param other the Decimal128 instance to copy from
     */
    public void copyRaw(Decimal128 other) {
        this.high = other.high;
        this.low = other.low;
    }

    /**
     * Divide this Decimal128 by another (in-place) with optimal precision
     *
     * @param divisor      The Decimal128 to divide by
     * @param scale        The decimal place
     * @param roundingMode The Rounding mode to use if the remainder is non-zero
     */
    public void divide(Decimal128 divisor, int scale, RoundingMode roundingMode) {
        divide(divisor.high, divisor.low, divisor.scale, scale, roundingMode);
    }

    /**
     * Divide this Decimal128 by another (in-place) with optimal precision
     *
     * @param scale        The decimal place
     * @param roundingMode The Rounding mode to use if the remainder is non-zero
     */
    public void divide(long divisorHigh, long divisorLow, int divisorScale, int scale, RoundingMode roundingMode) {
        validateScale(scale);
        // Compute the delta: how much power of 10 we should raise either the dividend or divisor.
        int delta = scale + (divisorScale - this.scale);

        // Fail early if we're sure to overflow.
        if (delta > 0 && (this.scale + delta) > MAX_SCALE) {
            throw NumericException.instance().put("Overflow in division: resulting scale would exceed maximum (" + MAX_SCALE + ")");
        }

        final boolean negResult = (divisorHigh < 0) ^ isNegative();

        // We're allowed to modify dividend as it will contain our result
        if (isNegative()) {
            negate();
        }

        // We cannot do the same for divisor, so we must reuse negate logic directly in our code to avoid
        // allocations.
        if (divisorHigh < 0) {
            divisorLow = ~divisorLow + 1;
            divisorHigh = ~divisorHigh + (divisorLow == 0 ? 1 : 0);
        }

        if (delta > 0) {
            // raise dividend to 10^delta
            multiplyByPowerOf10InPlace(delta);
        } else if (delta < 0) {
            // raise divisor to 10^(-delta), as we cannot modify the divisor, we use dividend to do it.
            long dividendHigh = this.high;
            long dividendLow = this.low;
            this.high = divisorHigh;
            this.low = divisorLow;
            multiplyByPowerOf10InPlace(-delta);
            divisorHigh = this.high;
            divisorLow = this.low;
            this.high = dividendHigh;
            this.low = dividendLow;
        }

        divider.clear();
        divider.ofDividend(high, low);
        divider.ofDivisor(divisorHigh, divisorLow);
        divider.divide(negResult, roundingMode);
        divider.sink(this, scale);

        if (negResult) {
            negate();
        }
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (!(obj instanceof Decimal128 other)) return false;

        final boolean isNull = isNull();
        final boolean otherIsNull = other.isNull();
        if (isNull != otherIsNull) {
            return false;
        } else if (isNull) {
            return true; // We don't need to compare scales for null values
        }
        return this.high == other.high &&
                this.low == other.low &&
                this.scale == other.scale;
    }

    /**
     * Extracts the digit at a specific power-of-ten position from a 128-bit decimal number.
     * <p>
     * Uses binary search to efficiently determine which digit (0-9) should appear at the
     * given power-of-ten position when the decimal is represented in base 10.
     * <p>
     * Prerequisites:
     * - The decimal value must be positive
     * - The decimal value must be less than 10^pow (e.g., for pow=3, decimal must be &lt; 10,000)
     *
     * @param pow the power of ten position to extract (0 = ones place, 1 = tens place, etc.)
     * @return the digit (0-9) at the specified power-of-ten position
     */
    public int getDigitAtPowerOfTen(int pow) {
        return getDigitAtPowerOfTen(high, low, pow);
    }

    /**
     * Gets the high 64 bits of the 128-bit decimal value.
     *
     * @return the high 64 bits of the decimal value
     */
    public long getHigh() {
        return high;
    }

    /**
     * Gets the low 64 bits of the 128-bit decimal value.
     *
     * @return the low 64 bits of the decimal value
     */
    public long getLow() {
        return low;
    }

    @Override
    public final int getMaxPrecision() {
        return MAX_PRECISION;
    }

    @Override
    public final int getMaxScale() {
        return MAX_SCALE;
    }

    /**
     * Gets the scale (number of decimal places) of this decimal value.
     *
     * @return the scale of this decimal value
     */
    public int getScale() {
        return scale;
    }

    public boolean hasOverflowed() {
        return compare(high, low, MAX_VALUE.high, MAX_VALUE.low) > 0
                || compareTo0(MIN_VALUE.high, MIN_VALUE.low) < 0;
    }

    /**
     * Returns a hash code for this decimal value.
     *
     * @return a hash code value for this object
     */
    @Override
    public int hashCode() {
        return Long.hashCode(high) ^ Long.hashCode(low) ^ Integer.hashCode(scale);
    }

    /**
     * Check if this number is negative
     */
    public boolean isNegative() {
        return high < 0;
    }

    /**
     * Returns whether this is null or not.
     *
     * @return true if null, false otherwise
     */
    public boolean isNull() {
        return high == Decimals.DECIMAL128_HI_NULL && low == Decimals.DECIMAL128_LO_NULL;
    }

    /**
     * Check if this number is zero
     */
    public boolean isZero() {
        return high == 0 && low == 0;
    }

    /**
     * Calculate modulo in-place.
     *
     * @param divisor the divisor
     * @throws NumericException if divisor is zero
     */
    public void modulo(Decimal128 divisor) {
        modulo(divisor.high, divisor.low, divisor.scale);
    }

    /**
     * Calculate modulo in-place.
     *
     * @throws NumericException if divisor is zero
     */
    public void modulo(long divisorHigh, long divisorLow, int divisorScale) {
        if (divisorHigh == 0 && divisorLow == 0) {
            throw NumericException.instance().put("Division by zero");
        }

        // Result scale should be the larger of the two scales
        int resultScale = Math.max(this.scale, divisorScale);

        // Use simple repeated subtraction for modulo: a % b = a - (a / b) * b
        // First compute integer division (a / b)
        // We store this for later usage
        long thisH = this.high;
        long thisL = this.low;
        int thisScale = this.scale;

        this.divide(divisorHigh, divisorLow, divisorScale, 0, RoundingMode.DOWN);

        // Now compute this * divisor
        this.multiply(divisorHigh, divisorLow, divisorScale);

        long qH = this.high;
        long qL = this.low;
        int qScale = this.scale;
        // restore this as a
        this.of(thisH, thisL, thisScale);
        // Finally compute remainder: a - (a / b) * b
        this.subtract(qH, qL, qScale);

        // Handle scale adjustment
        if (this.scale != resultScale) {
            rescale0(resultScale);
        }
    }

    /**
     * Multiply this Decimal128 by another (in-place)
     *
     * @param other The Decimal128 to multiply by
     */
    public void multiply(Decimal128 other) {
        multiply(other.high, other.low, other.scale);
    }

    /**
     * Negate this number in-place
     */
    public void negate() {
        if (isNull()) {
            return;
        }
        // Two's complement: invert all bits and add 1
        this.low = ~this.low + 1;
        this.high = ~this.high + (this.low == 0 ? 1 : 0);
    }

    /**
     * Sets this Decimal128 to the specified 128-bit value and scale.
     *
     * @param high  the high 64 bits (bits 64-127)
     * @param low   the low 64 bits (bits 0-63)
     * @param scale the number of decimal places
     */
    public void of(long high, long low, int scale) {
        this.high = high;
        this.low = low;
        this.scale = scale;
    }

    /**
     * Set this Decimal128 from a long value with the specified scale.
     *
     * @param value the long value
     * @param scale the desired scale
     */
    public void ofLong(long value, int scale) {
        validateScale(scale);

        this.scale = scale;
        this.low = value;
        this.high = value < 0 ? -1L : 0L;
    }

    /**
     * Set this Decimal128 to the null value. Zeroes out the scale.
     */
    public void ofNull() {
        high = Decimals.DECIMAL128_HI_NULL;
        low = Decimals.DECIMAL128_LO_NULL;
        scale = 0;
    }

    /**
     * Sets this Decimal128 to the specified 128-bit value. Keeps the existing scale.
     *
     * @param high the high 64 bits (bits 64-127)
     * @param low  the low 64 bits (bits 0-63)
     */
    public void ofRaw(long high, long low) {
        this.high = high;
        this.low = low;
    }

    /**
     * Sets this Decimal128 to the specified 64-bit value. Keeps the existing scale.
     *
     * @param value the value to sign-extend
     */
    public void ofRaw(long value) {
        this.high = value < 0 ? -1L : 0L;
        this.low = value;
    }

    /**
     * Set this Decimal128 to the null value. Zeroes out the scale.
     */
    public void ofRawNull() {
        high = Decimals.DECIMAL128_HI_NULL;
        low = Decimals.DECIMAL128_LO_NULL;
    }

    /**
     * Parses a CharSequence decimal and store the result into the given Decimal256.
     *
     * @param cs is the CharSequence to be parsed
     * @return the precision of the decimal
     */
    public long ofString(CharSequence cs) throws NumericException {
        return ofString(cs, -1, -1);
    }

    /**
     * Parses a CharSequence decimal and store the result into the given Decimal256.
     *
     * @param cs        is the CharSequence to be parsed
     * @param precision is the maximum precision that we allow when parsing or -1 if we don't want a limit
     * @param scale     is the final scale of our decimal, if the string has a bigger scale we will throw a NumericException
     * @return the precision of the decimal
     */
    public long ofString(CharSequence cs, int precision, int scale) throws NumericException {
        return ofString(cs, 0, cs.length(), precision, scale, false, false);
    }

    /**
     * Parses a CharSequence decimal and store the result into the given Decimal256.
     *
     * @param cs        is the CharSequence to be parsed
     * @param precision is the maximum precision that we allow when parsing or -1 if we don't want a limit
     * @param scale     is the final scale of our decimal, if the string has a bigger scale we will throw a NumericException
     * @param strict    determines whether we can strip tailing zeroes (making a different scale from the expected one)
     * @param lossy     allows to remove digits from the decimal after the dot to fit the provided scale
     * @return the precision and scale of the decimal, use {@link Numbers#decodeLowInt} to retrieve the precision and
     * {@link Numbers#decodeHighInt} to retrieve the scale
     */
    public long ofString(CharSequence cs, int lo, int hi, int precision, int scale, boolean strict, boolean lossy) throws NumericException {
        return DecimalParser.parse(this, cs, lo, hi, precision, scale, strict, lossy);
    }

    @Override
    public void ofZero() {
        of(0, 0, 0);
    }

    /**
     * Rescale this Decimal128 in place
     *
     * @param newScale The new scale (must be >= current scale)
     */
    public void rescale(int newScale) {
        validateScale(newScale);
        if (newScale < this.scale) {
            throw NumericException.instance().put("New scale must be >= current scale");
        }
        rescale0(newScale);
    }

    /**
     * Round this Decimal128 to the specified scale using the given rounding mode.
     * This method performs in-place rounding without requiring a divisor.
     *
     * @param targetScale  the desired scale (number of decimal places)
     * @param roundingMode the rounding mode to use
     * @throws NumericException if targetScale is invalid
     * @throws NumericException if roundingMode is UNNECESSARY and rounding is required
     */
    public void round(int targetScale, RoundingMode roundingMode) {
        if (isNull()) {
            return;
        }

        if (targetScale == this.scale) {
            // No rounding needed
            return;
        }

        validateScale(targetScale);

        if (isZero()) {
            this.scale = targetScale;
            return;
        }

        if (this.scale < targetScale) {
            boolean isNegative = isNegative();
            if (isNegative) {
                negate();
            }

            // Need to increase scale (add trailing zeros)
            int scaleIncrease = targetScale - this.scale;
            multiplyByPowerOf10InPlace(scaleIncrease);
            this.scale = targetScale;

            if (isNegative) {
                negate();
            }
            return;
        }

        divide(0, 1, 0, targetScale, roundingMode);
    }

    /**
     * Set the scale forcefully without doing any rescaling operations
     */
    public void setScale(int scale) {
        this.scale = scale;
    }

    /**
     * Subtract another Decimal128 from this one (in-place)
     *
     * @param other The Decimal128 to subtract
     */
    public void subtract(Decimal128 other) {
        subtract(other.high, other.low, other.scale);
    }

    /**
     * Subtract another Decimal128 from this one (in-place)
     *
     * @param bH     the high 64 bits of the other Decimal128
     * @param bL     the low 64 bits of the other Decimal128
     * @param bScale the number of decimal places of the other Decimal128
     */
    public void subtract(long bH, long bL, int bScale) {
        // Two's complement: invert all bits and add 1
        bL = ~bL + 1;
        bH = ~bH + (bL == 0 ? 1 : 0);

        add(this, this.high, this.low, this.scale, bH, bL, bScale);
    }

    /**
     * Subtracts a specific multiplier of a power of ten from the current 128-bit value.
     * This method modifies the value in-place by subtracting (multiplier * 10^pow).
     * <p>
     * Used in conjunction with getDigitAtPowerOfTen to extract individual digits:
     * 1. Call getDigitAtPowerOfTen to get the digit at a specific power
     * 2. Call this method to subtract that digit's contribution
     * 3. Repeat for the next lower power
     *
     * @param pow        the power of ten position
     * @param multiplier the digit to subtract (1-9, or 0 for no-op)
     */
    public void subtractPowerOfTenMultiple(int pow, int multiplier) {
        if (multiplier == 0 || multiplier > 9) {
            return;
        }

        // Get the value to subtract from POWERS_TEN_TABLE
        // Each power has 9 entries (for multipliers 1-9), each entry has 2 longs
        int offset = (multiplier - 1) * 2;

        // Perform 256-bit subtraction using two's complement
        // First, negate the value to subtract (two's complement)
        long bLow = ~POWERS_TEN_TABLE[pow][offset + 1] + 1;
        long c = bLow == 0L ? 1L : 0L;
        long r = low + bLow;
        long carry = hasCarry(low, r) ? 1L : 0L;
        low = r;

        long bHigh = ~POWERS_TEN_TABLE[pow][offset] + c;
        high = high + carry + bHigh;
    }

    /**
     * Convert to BigDecimal with full precision
     *
     * @return BigDecimal representation of this Decimal128
     */
    public java.math.BigDecimal toBigDecimal() {
        // Direct BigInteger construction is more efficient than string conversion
        byte[] bytes = new byte[16];
        // Convert to big-endian byte array
        for (int i = 0; i < 8; i++) {
            bytes[i] = (byte) (this.high >>> (56 - 8 * i));
            bytes[8 + i] = (byte) (this.low >>> (56 - 8 * i));
        }

        BigInteger unscaled = new BigInteger(bytes);
        return new BigDecimal(unscaled, this.scale);
    }

    @Override
    public void toDecimal256(Decimal256 decimal256) {
        if (isNull()) {
            decimal256.ofNull();
            return;
        }
        long s = high < 0 ? -1L : 0L;
        decimal256.of(s, s, high, low, scale);
    }

    /**
     * Convert to double (may lose precision).
     *
     * @return double representation
     */
    public double toDouble() {
        return toBigDecimal().doubleValue();
    }

    /**
     * Writes the string representation of this Decimal128 to the specified CharSink.
     * The output format is a plain decimal string without scientific notation.
     *
     * @param sink the CharSink to write to
     */
    @Override
    public void toSink(@NotNull CharSink<?> sink) {
        toSink(sink, high, low, scale);
    }

    /**
     * Returns the string representation of this Decimal128.
     * The output format is a plain decimal string without scientific notation.
     *
     * @return string representation of this decimal number
     */
    @Override
    public String toString() {
        StringSink sink = new StringSink(8);
        toSink(sink, high, low, scale);
        return sink.toString();
    }

    public void uncheckedAdd(Decimal128 other) {
        uncheckedAdd(other.high, other.low);
    }

    public void uncheckedAdd(long otherHigh, long otherLow) {
        uncheckedAdd(this, this.high, this.low, otherHigh, otherLow);
    }

    /**
     * Generic function to make a 128-bit addition.
     *
     * @param result Decimal128 that will store the result of the operation
     * @param aH     High 64-bit part of the first operand.
     * @param aL     Low 64-bit part of the first operand.
     * @param aScale Scale of the first operand.
     * @param bH     High 64-bit part of the second operand.
     * @param bL     Low 64-bit part of the second operand.
     * @param bScale Scale of the second operand.
     */
    private static void add(Decimal128 result, long aH, long aL, int aScale, long bH, long bL, int bScale) {
        result.scale = aScale;
        if (aScale < bScale) {
            // We need to rescale A to the same scale as B
            result.high = aH;
            result.low = aL;
            result.rescale0(bScale);
            aH = result.high;
            aL = result.low;
        } else if (aScale > bScale) {
            // We need to rescale B to the same scale as A
            result.high = bH;
            result.low = bL;
            result.scale = bScale;
            result.rescale0(aScale);
            bH = result.high;
            bL = result.low;
        }

        // Perform 128-bit addition
        long sumLow = aL + bL;

        // Check for carry
        long carry = hasCarry(aL, sumLow) ? 1 : 0;

        result.low = sumLow;
        try {
            result.high = Math.addExact(aH, Math.addExact(bH, carry));
        } catch (ArithmeticException e) {
            throw NumericException.instance().put("Overflow in addition: result exceeds 128-bit capacity");
        }
        if (result.hasOverflowed()) {
            throw NumericException.instance().put("Overflow in addition: result exceeds maximum precision");
        }
    }

    private static int compareToPowerOfTen(long aHi, long aLo, int pow, int multiplier) {
        final int offset = (multiplier - 1) * 2;
        long bHi = POWERS_TEN_TABLE[pow][offset];
        if (aHi != bHi) {
            return Long.compare(aHi, bHi);
        }
        long bLo = POWERS_TEN_TABLE[pow][offset + 1];
        return Long.compareUnsigned(aLo, bLo);
    }

    /**
     * Generic function to make a 128-bit addition assuming both values have the same scale.
     *
     * @param result Decimal128 that will store the result of the operation
     * @param aH     High 64-bit part of the first operand.
     * @param aL     Low 64-bit part of the first operand.
     * @param bH     High 64-bit part of the second operand.
     * @param bL     Low 64-bit part of the second operand.
     */
    private static void uncheckedAdd(Decimal128 result, long aH, long aL, long bH, long bL) {
        // Perform 128-bit addition
        long sumLow = aL + bL;

        // Check for carry
        long carry = hasCarry(aL, sumLow) ? 1 : 0;

        try {
            result.high = Math.addExact(aH, Math.addExact(bH, carry));
            // low is modified after high on purpose in order not to leave the result dirty
            // should addExact overflow
            result.low = sumLow;
        } catch (ArithmeticException e) {
            throw NumericException.instance().put("Overflow in addition: result exceeds 128-bit capacity");
        }
    }

    private static void uncheckedAdd(Decimal128 result, long bHi, long bLo) {
        long r = result.low + bLo;
        long carry = hasCarry(result.low, r) ? 1L : 0L;
        result.low = r;
        result.high += carry + bHi;
    }

    /**
     * Compare two longs as if they were unsigned.
     * Returns true iff one is bigger than two.
     */
    private static boolean unsignedLongCompare(long one, long two) {
        return (one + Long.MIN_VALUE) > (two + Long.MIN_VALUE);
    }

    /**
     * Validates that the scale is within allowed bounds
     */
    private static void validateScale(int scale) {
        // Use unsigned comparison for faster bounds check
        if (Integer.compareUnsigned(scale, MAX_SCALE) > 0) {
            throw NumericException.instance().put("Scale must be between 0 and " + MAX_SCALE + ", got: " + scale);
        }
    }

    private int compareTo0(long otherHi, long otherLo) {
        return compare(high, low, otherHi, otherLo);
    }

    private boolean hasUnsignOverflowed() {
        return compareTo0(MAX_VALUE.high, MAX_VALUE.low) > 0;
    }

    /**
     * Multiply this Decimal128 by another (in-place)
     *
     * @param bH     the high 64 bits of the other Decimal128
     * @param bL     the low 64 bits of the other Decimal128
     * @param bScale the number of decimal places of the other Decimal128
     */
    private void multiply(long bH, long bL, int bScale) {
        // Result scale is sum of scales
        int resultScale = this.scale + bScale;

        // Save the original signs before we modify anything
        boolean thisNegative = this.isNegative();
        boolean bNegative = bH < 0;

        // Convert to positive values for multiplication algorithm
        if (thisNegative) {
            negate();
        }

        // Get absolute value of other
        if (bNegative) {
            // Negate other's values
            bL = ~bL + 1;
            bH = ~bH + (bL == 0 ? 1 : 0);
        }

        if (bH == 0 && bL >= 0) {
            if (high == 0 && low >= 0) {
                multiply64By64Bit(bL);
            } else {
                multiplyBy64Bit(bL);
            }
        } else {
            multiplyBy128Bit(bH, bL);
        }

        if (hasUnsignOverflowed()) {
            throw NumericException.instance().put("Overflow in multiplication: result exceeds maximum precision");
        }

        // Handle sign - use the saved original signs
        boolean negative = (thisNegative != bNegative);
        if (negative) {
            // Negate result
            this.low = ~this.low + 1;
            this.high = ~this.high + (this.low == 0 ? 1 : 0);
        }

        this.scale = resultScale;
    }

    /**
     * Multiply this unsigned 64-bit value by an unsigned 64-bit value in place
     */
    private void multiply64By64Bit(long multiplier) {
        // Perform 128-bit × 64-bit multiplication
        // Result is at most 192 bits, but we keep only the lower 128 bits

        // Split multiplier into two 32-bit parts
        long m1 = multiplier >>> 32;
        long m0 = multiplier & 0xFFFFFFFFL;

        // Split this into four 32-bit parts
        long a1 = low >>> 32;
        long a0 = low & 0xFFFFFFFFL;

        // Compute partial products
        long p0 = a0 * m0;
        long p1 = a0 * m1 + a1 * m0;
        long p2 = a1 * m1;

        // Accumulate results
        long r0 = p0 & 0xFFFFFFFFL;
        long r1 = (p0 >>> 32) + (p1 & 0xFFFFFFFFL);
        long r2 = (r1 >>> 32) + (p1 >>> 32) + (p2 & 0xFFFFFFFFL);
        long r3 = (r2 >>> 32) + (p2 >>> 32);

        this.low = (r0 & 0xFFFFFFFFL) | ((r1 & 0xFFFFFFFFL) << 32);
        this.high = (r2 & 0xFFFFFFFFL) | ((r3 & 0xFFFFFFFFL) << 32);
    }

    /**
     * Multiply this unsigned 128-bit value by an unsigned 128-bit value in place
     */
    private void multiplyBy128Bit(long multiplierHigh, long multiplierLow) {
        // Perform multiplication using the algorithm from Decimal128
        // This is complex but avoids allocations
        long a3 = this.high >>> 32;
        long a2 = this.high & 0xFFFFFFFFL;
        long a1 = this.low >>> 32;
        long a0 = this.low & 0xFFFFFFFFL;

        long b3 = multiplierHigh >>> 32;
        long b2 = multiplierHigh & 0xFFFFFFFFL;
        long b1 = multiplierLow >>> 32;
        long b0 = multiplierLow & 0xFFFFFFFFL;

        // Compute all partial products
        long p00 = a0 * b0;
        long p01 = a0 * b1;
        long p02 = a0 * b2;
        long p03 = a0 * b3;
        long p10 = a1 * b0;
        long p11 = a1 * b1;
        long p12 = a1 * b2;
        long p13 = a1 * b3;
        long p20 = a2 * b0;
        long p21 = a2 * b1;
        long p22 = a2 * b2;
        long p23 = a2 * b3;
        long p30 = a3 * b0;
        long p31 = a3 * b1;
        long p32 = a3 * b2;
        long p33 = a3 * b3;

        // Gather results into 128-bit result
        long r0 = (p00 & LONG_MASK);
        long r1 = (p00 >>> 32) + (p01 & LONG_MASK) + (p10 & LONG_MASK);
        long r2 = (r1 >>> 32) + (p01 >>> 32) + (p10 >>> 32) +
                (p02 & LONG_MASK) + (p11 & LONG_MASK) + (p20 & LONG_MASK);
        long r3 = (r2 >>> 32) + (p02 >>> 32) + (p11 >>> 32) + (p20 >>> 32) +
                (p03 & LONG_MASK) + (p12 & LONG_MASK) + (p21 & LONG_MASK) + (p30 & LONG_MASK);
        long r4 = (r3 >>> 32) + (p03 >>> 32) + (p12 >>> 32) + (p21 >>> 32) + (p30 >>> 32) +
                (p13 & LONG_MASK) + (p22 & LONG_MASK) + (p31 & LONG_MASK);
        long r5 = (r4 >>> 32) + (p13 >>> 32) + (p22 >>> 32) + (p31 >>> 32) +
                (p23 & LONG_MASK) + (p32 & LONG_MASK);
        long r6 = (r5 >>> 32) + (p23 >>> 32) + (p32 >>> 32) +
                (p33 & LONG_MASK);

        if ((r3 >>> 31) != 0 || r4 != 0 || r5 != 0 || r6 != 0) {
            throw NumericException.instance().put("Overflow in multiplication (128-bit × 128-bit): product exceeds 128-bit capacity");
        }

        this.low = (r0 & 0xFFFFFFFFL) | ((r1 & 0xFFFFFFFFL) << 32);
        this.high = (r2 & 0xFFFFFFFFL) | ((r3 & 0xFFFFFFFFL) << 32);
    }

    /**
     * Multiply this unsigned 128-bit value by an unsigned 128-bit value in place without checking for overflow
     */
    private void multiplyBy128BitUnchecked(long multiplierHigh, long multiplierLow) {
        // Perform multiplication using the algorithm from Decimal128
        // This is complex but avoids allocations
        long a3 = this.high >>> 32;
        long a2 = this.high & 0xFFFFFFFFL;
        long a1 = this.low >>> 32;
        long a0 = this.low & 0xFFFFFFFFL;

        long b3 = multiplierHigh >>> 32;
        long b2 = multiplierHigh & 0xFFFFFFFFL;
        long b1 = multiplierLow >>> 32;
        long b0 = multiplierLow & 0xFFFFFFFFL;

        // Compute all partial products
        long p00 = a0 * b0;
        long p01 = a0 * b1;
        long p02 = a0 * b2;
        long p03 = a0 * b3;
        long p10 = a1 * b0;
        long p11 = a1 * b1;
        long p12 = a1 * b2;
        long p20 = a2 * b0;
        long p21 = a2 * b1;
        long p30 = a3 * b0;

        // Gather results into 128-bit result
        long r0 = (p00 & LONG_MASK);
        long r1 = (p00 >>> 32) + (p01 & LONG_MASK) + (p10 & LONG_MASK);
        long r2 = (r1 >>> 32) + (p01 >>> 32) + (p10 >>> 32) +
                (p02 & LONG_MASK) + (p11 & LONG_MASK) + (p20 & LONG_MASK);
        long r3 = (r2 >>> 32) + (p02 >>> 32) + (p11 >>> 32) + (p20 >>> 32) +
                (p03 & LONG_MASK) + (p12 & LONG_MASK) + (p21 & LONG_MASK) + (p30 & LONG_MASK);

        this.low = (r0 & 0xFFFFFFFFL) | ((r1 & 0xFFFFFFFFL) << 32);
        this.high = (r2 & 0xFFFFFFFFL) | ((r3 & 0xFFFFFFFFL) << 32);
    }

    /**
     * Multiply this unsigned 128-bit value by an unsigned 64-bit value in place
     */
    private void multiplyBy64Bit(long multiplier) {
        // Perform 128-bit × 64-bit multiplication
        // Result is at most 192 bits, but we keep only the lower 128 bits

        // Split multiplier into two 32-bit parts
        long m1 = multiplier >>> 32;
        long m0 = multiplier & 0xFFFFFFFFL;

        // Split this into four 32-bit parts
        long a3 = high >>> 32;
        long a2 = high & 0xFFFFFFFFL;
        long a1 = low >>> 32;
        long a0 = low & 0xFFFFFFFFL;

        // Compute partial products
        long p0 = a0 * m0;
        long p1 = a0 * m1 + a1 * m0;
        long p2 = a1 * m1 + a2 * m0;
        long p3 = a2 * m1 + a3 * m0;
        long p4 = a3 * m1;

        // Accumulate results
        long r0 = p0 & 0xFFFFFFFFL;
        long r1 = (p0 >>> 32) + (p1 & 0xFFFFFFFFL);
        long r2 = (r1 >>> 32) + (p1 >>> 32) + (p2 & 0xFFFFFFFFL);
        long r3 = (r2 >>> 32) + (p2 >>> 32) + (p3 & 0xFFFFFFFFL);
        long r4 = (r3 >>> 32) + (p3 >>> 32) + p4;

        // Check for overflow: if r4 has significant bits, the result exceeds 128 bits
        if (r4 != 0 || (r3 >> 31) != 0) {
            throw NumericException.instance().put("Overflow in multiplication (128-bit × 64-bit): product exceeds 128-bit capacity");
        }

        this.low = (r0 & 0xFFFFFFFFL) | ((r1 & 0xFFFFFFFFL) << 32);
        this.high = (r2 & 0xFFFFFFFFL) | ((r3 & 0xFFFFFFFFL) << 32);
    }

    /**
     * Multiply this unsigned 128-bit value by an unsigned 64-bit value in place without checking for overflow
     */
    private void multiplyBy64BitUnchecked(long multiplier) {
        // Perform 128-bit × 64-bit multiplication
        // Result is at most 192 bits, but we keep only the lower 128 bits

        // Split multiplier into two 32-bit parts
        long m1 = multiplier >>> 32;
        long m0 = multiplier & 0xFFFFFFFFL;

        // Split this into four 32-bit parts
        long a3 = high >>> 32;
        long a2 = high & 0xFFFFFFFFL;
        long a1 = low >>> 32;
        long a0 = low & 0xFFFFFFFFL;

        // Compute partial products
        long p0 = a0 * m0;
        long p1 = a0 * m1 + a1 * m0;
        long p2 = a1 * m1 + a2 * m0;
        long p3 = a2 * m1 + a3 * m0;

        // Accumulate results
        long r0 = p0 & 0xFFFFFFFFL;
        long r1 = (p0 >>> 32) + (p1 & 0xFFFFFFFFL);
        long r2 = (r1 >>> 32) + (p1 >>> 32) + (p2 & 0xFFFFFFFFL);
        long r3 = (r2 >>> 32) + (p2 >>> 32) + (p3 & 0xFFFFFFFFL);

        this.low = (r0 & 0xFFFFFFFFL) | ((r1 & 0xFFFFFFFFL) << 32);
        this.high = (r2 & 0xFFFFFFFFL) | ((r3 & 0xFFFFFFFFL) << 32);
    }

    /**
     * Multiply this (unsigned) by 10^n in place
     */
    private void multiplyByPowerOf10InPlace(int n) {
        if (n <= 0 || (high == 0 && low == 0)) {
            return;
        }

        if (n >= 38) {
            throw NumericException.instance().put("Overflow in scale adjustment: multiplying by 10^" + n + " exceeds 128-bit capacity");
        }

        // For larger powers, break down into smaller chunks, first check the threshold to ensure that we won't overflow
        // and then apply either a fast multiplyBy64 or multiplyBy128 depending on high/low.
        // The bound checks for these tables already happens at the beginning of the method.
        final long[] thresholds = POWERS_TEN_TABLE_THRESHOLDS[n - 1];

        if (high > thresholds[0] || (high == thresholds[0] && unsignedLongCompare(low, thresholds[1]))) {
            throw NumericException.instance().put("Overflow in scale adjustment: multiplying by 10^" + n + " exceeds 128-bit capacity");
        }

        final long multiplierHigh = n >= 20 ? TEN_POWERS_TABLE_HIGH[n - 20] : 0L;
        final long multiplierLow = TEN_POWERS_TABLE_LOW[n];
        if (multiplierHigh == 0L && multiplierLow >= 0L) {
            if (high == 0 && low >= 0L) {
                multiply64By64Bit(multiplierLow);
            } else {
                multiplyBy64BitUnchecked(multiplierLow);
            }
        } else {
            multiplyBy128BitUnchecked(multiplierHigh, multiplierLow);
        }
    }

    private void rescale0(int resultScale) {
        int scaleUp = resultScale - this.scale;
        boolean isNegative = isNegative();
        if (isNegative) {
            negate();
        }
        multiplyByPowerOf10InPlace(scaleUp);
        if (isNegative) {
            negate();
        }
        this.scale = resultScale;
    }

    /**
     * Check if addition resulted in a carry
     * When adding two unsigned numbers a + b = sum, carry occurs iff sum < a (or sum < b)
     * This works because:
     * - No carry: sum = a + b, so sum >= a and sum >= b
     * - Carry: sum = a + b - 2^64, so sum < a and sum < b
     */
    static boolean hasCarry(long a, long sum) {
        // We can check against either a or b - both work
        // Using a for consistency, b parameter kept for clarity
        return (sum + Long.MIN_VALUE) < (a + Long.MIN_VALUE);
    }
}
